package com.example.redis.autoregister.config;

import com.example.redis.autoregister.RedisSerializerFactory;
import com.example.redis.autoregister.RedisSerializerFactoryContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

public class RedisTemplateAutoRegister implements BeanPostProcessor {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    // 在 bean 完成注入，但还没有进行初始化时进行回到
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
        // 遍历 bean 中的属性
        ReflectionUtils.doWithFields(bean.getClass(), field -> {
            RedisTemplateConfig redisTemplateConfig = AnnotationUtils.getAnnotation(field, RedisTemplateConfig.class);
            if (redisTemplateConfig == null){
                return;
            }
            // 如果字段配置了 RedisTemplateConfig，则进行功能加强

            // 1. 获取 RedisTemplate 信息
            RedisTemplateInfo redisTemplateInfo = getRedisTemplateInfo(field);
            // 2. 创建 RedisTemplate
            RedisTemplate redisTemplate = createRedisTemplate(redisTemplateConfig, redisTemplateInfo);
            // 3. 将 RedisTemplate 写回属性
            field.setAccessible(true);
            ReflectionUtils.setField(field, bean, redisTemplate);
        });
        return bean;
    }

    // 创建 RedisTemplate
    private RedisTemplate createRedisTemplate(RedisTemplateConfig redisTemplateConfig, RedisTemplateInfo redisTemplateInfo) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(createSerializer(redisTemplateConfig.keySerializer(), redisTemplateInfo));
        redisTemplate.setValueSerializer(createSerializer(redisTemplateConfig.valueSerializer(), redisTemplateInfo));
        if (redisTemplateConfig.hashType()){
            redisTemplate.setHashKeySerializer(createSerializer(redisTemplateConfig.hashKeySerializer(), redisTemplateInfo));
            redisTemplate.setHashValueSerializer(createSerializer(redisTemplateConfig.hashValueSerializer(), redisTemplateInfo));
        }

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    // 创建 RedisSerializer
    private RedisSerializer<?> createSerializer(RedisSerializerBuilderConfig redisSerializerBuilderConfig, RedisTemplateInfo redisTemplateInfo) {
        Class builderClass = redisSerializerBuilderConfig.builder();
        RedisSerializerFactory builder = null;
        try {
            builder = (RedisSerializerFactory) builderClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        Map<String, String> params = getParams(redisSerializerBuilderConfig.initParams());
        RedisSerializerFactoryContext builderContext = new RedisSerializerFactoryContext(redisTemplateInfo.getKey(), redisTemplateInfo.getValue(), params);

        return builder.create(builderContext);
    }

    private Map<String, String> getParams(RedisSerializerBuilderConfigParam[] redisSerializerBuilderConfigParams) {
        Map<String, String> params = new HashMap<>();
        for (RedisSerializerBuilderConfigParam param : redisSerializerBuilderConfigParams){
            params.put(param.key(), param.value());
        }
        return params;
    }

    // 获取 RedisTemplate 类型信息
    private RedisTemplateInfo getRedisTemplateInfo(Field field) {
        ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
        Type[] types = parameterizedType.getActualTypeArguments();
        if (types.length != 2){
            return null;
        }
        return new RedisTemplateInfo((Class)types[0], (Class)types[1]);
    }
}
